package jk.fox.common;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Auther cuichao
 * @Description: ReflectionUtils
 * @Date: create in 2020/4/19 13:44
 */
public class ReflectionUtils {

    public static final Map<String,Class> primitiveMap = new HashMap<>();

    static {
        primitiveMap.put(int.class.getName(),int.class);
        primitiveMap.put(short.class.getName(),short.class);
        primitiveMap.put(float.class.getName(),float.class);
        primitiveMap.put(double.class.getName(),double.class);
        primitiveMap.put(long.class.getName(),long.class);
        primitiveMap.put(byte.class.getName(),byte.class);
        primitiveMap.put(boolean.class.getName(),boolean.class);
    }



    public static Method searchMethod(Object target, String methodName, String[] parametersTypes) throws Exception {
        /**
         * 高并发有可能导致 软引用的反射对象被GC
         * GC后被回收后需要DelegatingClassloader
         * 加载一个生成反射类到方法区G1以前的GC是不会
         * 回收永久区只能fullGC造成STW
         */
        Method[] methods = target.getClass().getMethods();
        List<Method> targetMethod = new ArrayList<>();
        for (Method method : methods) {
            boolean isMatch = matchMethod(method,methodName,parametersTypes);
            if(isMatch){
                targetMethod.add(method);
            }
        }
        if(targetMethod.size() == 0){
            throw new NoSuchMethodException(String.format("{%s}找不到该方法",methodName));
        }
        if(targetMethod.size() != 1 ){
            throw new RuntimeException("无法识别多个方法重载");
        }
        return targetMethod.get(0);
    }


    private static boolean matchMethod(Method method,String methodName,String[] parametersTypes) throws ClassNotFoundException {
        if(!method.getName().equals(methodName)){
            return false;
        }
        return matchParameter(method.getParameters(),parametersTypes);
    }


    private static boolean matchParameter(Parameter[] parameters, String[] parametersTypes) throws ClassNotFoundException {
        if(parametersTypes.length != parameters.length){
            return false;
        }
        int len = parametersTypes.length;

        for(int i=0; i<len; i++){
            String actualType = parametersTypes[i];
            //形参
            Class<?> type = parameters[i].getType();
            //实参
            Class actual = primitiveMap.get(actualType);

            if(actual == null){
                actual = Class.forName(actualType);
            }
            //判断类型是否相同
            if(actual == type){
                continue;
            }
            //判断实参是否为形参的子类
            if(type.isAssignableFrom(actual)){
                continue;
            }
            /**
             * 实参是否是基础类型且形参为包装类型 == 实参的包装类型.Type
             * 实参类型==形参类型 || 实现类型 为 实参类型的包装类
             */
            if(actual.isPrimitive() && !type.isPrimitive()){
                try {
                    Object Wrapper = type.getField("TYPE").get(null);
                    if(Wrapper == actual ){
                        continue;
                    }
                } catch (Exception e) {
                    return false;
                }
            }

            /**
             * 形参是否是基础类型且实参为包装类型 == 实参的包装类型.Type
             * 实参类型==形参类型 || 实现类型 为 实参类型的包装类
             */
            if(type.isPrimitive() && !actual.isPrimitive()){
                try {
                    Object Wrapper = actual.getField("TYPE").get(null);
                    if(Wrapper == type ){
                        continue;
                    }
                } catch (Exception e) {
                    return false;
                }
            }
           /**
             * 比对泛型类型
            */
            //获取不到ignore
        }
        return true;
    }


    public static String[] getParameterTypes(Method method){
        Parameter[] parameters = method.getParameters();
        String[] types = Arrays.stream(parameters).map(v -> v.getType().getName()).collect(Collectors.toList()).toArray(new String[parameters.length]);
        return types;
    }
}
